TypeScript · 6983 bytes Raw Blame History
1 'use client';
2
3 import { useState, useEffect, useRef } from 'react';
4 import Link from 'next/link';
5 import { useParams } from 'next/navigation';
6 import { getConflicts, getPeopleByConflict, Conflict, PersonDetail } from '@/lib/api';
7 import Header from '@/components/Header';
8 import DocumentIcon from '@/components/DocumentIcon';
9 import AwardIcon from '@/components/AwardIcon';
10 import Pagination from '@/components/Pagination';
11
12 export default function ConflictPage() {
13 const params = useParams();
14 const conflictId = parseInt(params.id as string);
15
16 const [conflict, setConflict] = useState<Conflict | null>(null);
17 const [people, setPeople] = useState<PersonDetail[]>([]);
18 const [loading, setLoading] = useState(true);
19 const [error, setError] = useState<string | null>(null);
20 const [currentPage, setCurrentPage] = useState(1);
21 const [itemsPerPage, setItemsPerPage] = useState(30);
22
23 // Ref for scrolling to top of results list
24 const resultsRef = useRef<HTMLDivElement>(null);
25
26 useEffect(() => {
27 async function fetchData() {
28 try {
29 const conflicts = await getConflicts();
30 const currentConflict = conflicts.find(c => c.id === conflictId);
31
32 if (!currentConflict) {
33 throw new Error('Conflict not found');
34 }
35
36 setConflict(currentConflict);
37 const peopleData = await getPeopleByConflict(conflictId);
38 setPeople(peopleData);
39 } catch (err) {
40 setError(err instanceof Error ? err.message : 'Failed to load data');
41 console.error(err);
42 } finally {
43 setLoading(false);
44 }
45 }
46
47 fetchData();
48 }, [conflictId]);
49
50 // Calculate paginated data
51 const totalItems = people.length;
52 const startIndex = (currentPage - 1) * itemsPerPage;
53 const endIndex = startIndex + itemsPerPage;
54 const paginatedPeople = people.slice(startIndex, endIndex);
55
56 // Handlers for pagination
57 const handlePageChange = (page: number) => {
58 setCurrentPage(page);
59 // Scroll to top of results list
60 resultsRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
61 };
62
63 const handleItemsPerPageChange = (newItemsPerPage: number) => {
64 setItemsPerPage(newItemsPerPage);
65 setCurrentPage(1); // Reset to first page when changing items per page
66 };
67
68 if (loading) {
69 return (
70 <div className="min-h-screen bg-vmi-cream flex items-center justify-center">
71 <p className="text-gray-600 text-xl">Loading...</p>
72 </div>
73 );
74 }
75
76 if (error || !conflict) {
77 return (
78 <div className="min-h-screen bg-vmi-cream flex items-center justify-center">
79 <div className="text-center">
80 <p className="text-red-600 mb-4 text-xl">{error || 'Conflict not found'}</p>
81 <Link href="/" className="text-vmi-red hover:text-vmi-dark-red underline font-semibold">
82 Return to Home
83 </Link>
84 </div>
85 </div>
86 );
87 }
88
89 return (
90 <div className="min-h-screen bg-vmi-cream">
91 <Header
92 breadcrumbs={[
93 { label: 'Home', href: '/' },
94 { label: conflict.name }
95 ]}
96 />
97
98 {/* Main Content */}
99 <main className="max-w-6xl mx-auto px-4 py-12">
100 {/* Conflict Header */}
101 <div className="bg-vmi-light-gold border-2 border-vmi-gold rounded-lg p-8 mb-12 shadow-xl">
102 <h1 className="text-4xl font-black text-vmi-red mb-4">
103 {conflict.name}
104 </h1>
105 <p className="text-xl text-gray-700 mb-4">
106 {conflict.start_year === conflict.end_year
107 ? conflict.start_year
108 : `${conflict.start_year}${conflict.end_year || 'Present'}`}
109 </p>
110 {conflict.description && (
111 <p className="text-gray-800 leading-relaxed mb-6">{conflict.description}</p>
112 )}
113 <div className="border-t-2 border-vmi-gold pt-6">
114 <p className="text-2xl font-bold text-vmi-red">
115 {conflict.casualty_count} VMI Alumni Gave Their Lives
116 </p>
117 </div>
118 </div>
119
120 {/* People List */}
121 <div ref={resultsRef} className="bg-white border-2 border-gray-300 rounded-lg p-8 shadow-xl">
122 <h2 className="text-3xl font-bold mb-8 text-center text-vmi-red">
123 Honor Roll
124 </h2>
125
126 {people.length === 0 ? (
127 <p className="text-center text-gray-600 text-lg">No casualties recorded yet.</p>
128 ) : (
129 <>
130 {/* Pagination Controls - Top */}
131 <Pagination
132 currentPage={currentPage}
133 totalItems={totalItems}
134 itemsPerPage={itemsPerPage}
135 onPageChange={handlePageChange}
136 onItemsPerPageChange={handleItemsPerPageChange}
137 />
138
139 {/* People Grid */}
140 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
141 {paginatedPeople.map((person) => (
142 <Link
143 key={person.id}
144 href={`/memorial/person/${person.id}`}
145 className="block p-6 border-2 border-gray-200 rounded-lg hover:border-vmi-gold hover:bg-vmi-light-gold transition-all duration-200 group"
146 >
147 <h3 className="text-xl font-bold text-gray-800 group-hover:text-vmi-red transition-colors mb-2 flex items-center gap-2">
148 {(() => {
149 const name = person.full_display_name || person.display_name;
150 // Only remove rank if it exists
151 if (person.rank) {
152 return name.replace(person.rank + ' ', '').replace(person.rank + ', ', '');
153 }
154 return name;
155 })()}
156 {person.has_awards && <AwardIcon className="flex-shrink-0" />}
157 {person.pdf_key && <DocumentIcon className="flex-shrink-0" />}
158 </h3>
159 {person.rank && (
160 <p className="text-gray-700 font-semibold">{person.rank}</p>
161 )}
162 {person.unit && (
163 <p className="text-gray-600 text-sm italic">{person.unit}</p>
164 )}
165 {person.death_description && (
166 <p className="text-gray-600 text-sm italic mt-3 line-clamp-3">
167 {person.death_description}
168 </p>
169 )}
170 </Link>
171 ))}
172 </div>
173
174 {/* Pagination Controls - Bottom */}
175 <Pagination
176 currentPage={currentPage}
177 totalItems={totalItems}
178 itemsPerPage={itemsPerPage}
179 onPageChange={handlePageChange}
180 onItemsPerPageChange={handleItemsPerPageChange}
181 />
182 </>
183 )}
184 </div>
185 </main>
186 </div>
187 );
188 }